﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;
using System.Xml.Linq;
using System.Web;
using WebHelper.Helpers;
using System.Web.Hosting;

namespace WebHelper.TraceListeners
{
    [HostProtection(SecurityAction.LinkDemand, Synchronization = true)]
    [Serializable]
    public class CustomTraceListener : TraceListener
    {
        public static string Register(bool writeToWebConfig, string configPath,string folderPath, string fileName,int maxFilePerFolder ,int maxItemsPerFile)
        {
            if (string.IsNullOrEmpty(configPath))
            {
                configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(configPath);
            XElement trace = XmlHelper.CreateIfNotExist(document.Root, "system.diagnostics", "trace", "listeners");
            var customtrace = trace.Elements("remove").FirstOrDefault(e => e.Attribute("name") != null && e.Attribute("name").Value.Contains(typeof(CustomTraceListener).FullName));
            if (customtrace == null)
            {
                if (trace.Elements("remove").Count() > 0)
                {
                    trace.Elements("remove").Last().AddAfterSelf(new XElement("remove", new XAttribute("name", typeof(CustomTraceListener).FullName)));
                }
                else
                {
                    trace.AddFirst(new XElement("remove", new XAttribute("name", typeof(CustomTraceListener).FullName)));
                }
            }
            customtrace = trace.Elements("add").FirstOrDefault(e => e.Attribute("type") != null && e.Attribute("type").Value.Contains(typeof(CustomTraceListener).FullName));
            XElement element = new XElement("add",
                new XAttribute("name", typeof(CustomTraceListener).FullName),
                new XAttribute("type", typeof(CustomTraceListener).FullName + "," + typeof(CustomTraceListener).Assembly.FullName),
                new XAttribute("folderPath", folderPath),
                new XAttribute("fileName", fileName),
                new XAttribute("maxFilePerFolder", maxFilePerFolder),
                new XAttribute("maxItemsPerFile", maxItemsPerFile));
            if (customtrace == null)
            {
                trace.Add(element);
            }

            if (writeToWebConfig)
            {
                document.Save(configPath);
            }

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "register in web.config", typeof(CustomTraceListener).Name + "  ");

            return element.ToString();
        }
        public static void UnRegister(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement trace = XmlHelper.GetElementsOfPath(document.Root, "system.diagnostics", "trace", "listeners").Elements("remove").FirstOrDefault(e => e.Attribute("name").Value.Contains(typeof(CustomTraceListener).FullName));
            if (trace != null)
            {
                trace.Remove();
            }
            trace = XmlHelper.GetElementsOfPath(document.Root, "system.diagnostics", "trace", "listeners").Elements("add").FirstOrDefault(e => e.Attribute("type").Value.Contains(typeof(CustomTraceListener).FullName));
            if (trace != null)
            {
                trace.Remove();
            }
            document.Save(filePath);

            //trace
            Trace.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "unRegister from web.Congig", typeof(CustomTraceListener).Name + "  ");

        }
        public static void ForwardWebTrace(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                filePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }
            XDocument document = XDocument.Load(filePath);
            XElement trace = XmlHelper.CreateIfNotExist(document.Root, "system.web", "trace");
            trace.SetAttributeValue("writeToDiagnosticsTrace","true");
            document.Save(filePath);
        }



        public string FolderPath
        {
            get
            {
                if (this.Attributes["folderPath"].StartsWith("~"))
                {
                    return HostingEnvironment.MapPath(this.Attributes["folderPath"]);
                }
                else
                {
                    return this.Attributes["folderPath"];
                }
            }
        }
        public string FileName
        {
            get
            {
                return this.Attributes["fileName"];
            }
        }
        public int MaxFilePerFolder
        {
            get
            {
                return Convert.ToInt32(this.Attributes["maxFilePerFolder"]);
            }
        }
        public int MaxItemsPerFile
        {
            get
            {
                return Convert.ToInt32(this.Attributes["maxItemsPerFile"]);
            }
        }
        private int f_CurrentItemNumber = 0;
        private int f_CurrentFileNumber=0;
        private string f_CurrentFilePath = "";


        private Dictionary<string, string> f_buffer = new Dictionary<string, string>();

        private void deleteExceededFiles()
        {
            //check for limited file per folder
            if (this.MaxFilePerFolder > 0)
            {
                //delete the exceeded number
                string[] files = Directory.GetFiles(this.FolderPath, "*.log").OrderBy(i => i).ToArray();
                if ((files.Count() - this.MaxFilePerFolder+1) > 0)
                {
                    string[] filesToDelete = files.Take(files.Count() - this.MaxFilePerFolder+1).ToArray();
                    for (int i = 0; i < filesToDelete.Count(); i++)
                    {
                        File.Delete(filesToDelete.ElementAt(i));
                    }
                }
            }
        }
        private void addToBuffer(string category, string message)
        {
            if (this.f_buffer.ContainsKey(category.ToLower()))
            {
                this.f_buffer[category.ToLower()] = this.f_buffer[category.ToLower()] + message;
            }
            else
            {
                this.f_buffer.Add(category.ToLower(), message);
            }
        }
        private string ClearCategory(string category)
        {
            string s = "";
            if (this.f_buffer.ContainsKey(category.ToLower()))
            {
                s = this.f_buffer[category.ToLower()];
                this.f_buffer.Remove(category.ToLower());
            }
            return s;
        }
        private void writeTraceInformation(string message, string category, TraceLevel level)
        {

            //check for directory
            if (!Directory.Exists(this.FolderPath))
            {
                Directory.CreateDirectory(this.FolderPath);
            }
            //check for file
            if (this.f_CurrentFilePath == "")
            {
                this.f_CurrentFilePath = this.FolderPath + "\\" + this.FileName + DateTime.Now.ToString(".yyyy.MM.dd.HH.mm.ss.ffff.") + (this.f_CurrentFileNumber + 1) + ".log";
                this.deleteExceededFiles();
            }

            
            
            //check for limited number of items
            if (this.MaxItemsPerFile > 0)
            {
                //compare the current item Number with maximum number per file
                if (this.f_CurrentItemNumber >= this.MaxItemsPerFile)
                {
                    //create new file
                    this.f_CurrentFileNumber++;
                    this.f_CurrentItemNumber = 0;
                    this.f_CurrentFilePath = this.FolderPath + "\\" + this.FileName + DateTime.Now.ToString(".yyyy.MM.dd.HH.mm.ss.ffff.") + (this.f_CurrentFileNumber + 1) + ".log";
                    this.deleteExceededFiles();

                }
            }//else unlimited per file

            //write item
            StackTrace st = new StackTrace(true);

            XElement element = new XElement("TraceItem",
                new XAttribute("Date", DateTime.Now),
                new XAttribute("Category", category),
                new XAttribute("Level", level),
                new XAttribute("Message", message),
                new XAttribute("StackTrace", st.ToString()));
            File.AppendAllText(this.f_CurrentFilePath, element.ToString(SaveOptions.DisableFormatting)+"\r\n\r\n");
            this.f_CurrentItemNumber++;
        }
        
        public CustomTraceListener(): base(typeof(CustomTraceListener).Name)
        {

        }
        
        /// <summary>
        /// emit error info to file
        /// </summary>
        public override void Fail(string message)
        {
            this.writeTraceInformation(message, "", TraceLevel.Error);
        }
        
        /// <summary>
        /// emit error info to file
        /// </summary>
        public override void Fail(string category, string message)
        {
            this.writeTraceInformation(message, category, TraceLevel.Error);
        }

        /// <summary>
        /// emit error info to the buffer
        /// </summary>
        public override void Write(object o)
        {
            this.addToBuffer("", o.ToString());
        }

        /// <summary>
        /// emit error info to the buffer
        /// </summary>
        public override void Write(string message)
        {
            this.addToBuffer("", message.ToString());
        }

        /// <summary>
        /// emit error info to the buffer
        /// </summary>
        public override void Write(object o, string category)
        {
            this.addToBuffer(category, o.ToString());
        }

        /// <summary>
        /// emit error info to the buffer
        /// </summary>
        public override void Write(string message, string category)
        {
            this.addToBuffer(category, message.ToString());
        }

        /// <summary>
        /// emit error info to the file directly 
        /// </summary>
        public override void WriteLine(object o)
        {
            string s = this.ClearCategory("");
            this.writeTraceInformation(s + o.ToString(), "", TraceLevel.Info);
        }
        
        /// <summary>
        /// emit error info to the file directly 
        /// </summary>
        public override void WriteLine(object o, string category)
        {
            string s = this.ClearCategory("");
            this.writeTraceInformation(s + o.ToString(), category, TraceLevel.Info);
        }

        /// <summary>
        /// emit error info to the file directly 
        /// </summary>
        public override void WriteLine(string message)
        {
            
            string s = this.ClearCategory("");
            this.writeTraceInformation(s + message, "", TraceLevel.Info);
        }

        /// <summary>
        /// emit error info to the file directly 
        /// </summary>
        public override void WriteLine(string message, string category)
        {
            string s = this.ClearCategory("");
            this.writeTraceInformation(s+message, "", TraceLevel.Info);
        }

        public override void Flush()
        {
            for (int i = 0; i < this.f_buffer.Count; i++)
			{
                string category = this.f_buffer.Keys.ElementAt(i);
                string message = this.f_buffer[category];
                this.writeTraceInformation(message, category, TraceLevel.Info);
			}
            this.f_buffer.Clear();
        }

        protected override string[] GetSupportedAttributes()
        {
            return new string[] { "folderPath", "fileName","maxFilePerFolder", "maxItemsPerFile"};
        }

    }
}
